import { nextTick, onBeforeUnmount, ref, watch, type Ref } from "vue";

export interface UseListKeyboardNavOptions<T> {
  items: Ref<T[]>;
  onSelect: (item: T) => void;
  onEscapeFocus?: () => void;
}

export interface UseListKeyboardNavResult {
  selectedIndex: Ref<number | null>;
  setItemRef: (el: HTMLElement | null, index: number) => void;
  setContainerRef: (el: HTMLElement | null) => void;
  onInputKeydown: (e: KeyboardEvent) => void;
  onListKeydown: (e: KeyboardEvent) => void;
  focusFirst: () => void;
}

export function useListKeyboardNav<T>(
  options: UseListKeyboardNavOptions<T>
): UseListKeyboardNavResult {
  const { items, onSelect, onEscapeFocus } = options;

  const selectedIndex = ref<number | null>(null);
  const pendingFocusAfterLoad = ref<boolean>(false);
  const itemRefs = new Map<number, HTMLElement>();
  const containerRef = ref<HTMLElement | null>(null);

  function clampIndex(index: number): number {
    const max = items.value.length - 1;
    if (max < 0) return -1;
    if (index < 0) return 0;
    if (index > max) return max;
    return index;
  }

  function focusItemAt(index: number) {
    const clamped = clampIndex(index);
    if (clamped < 0) return;
    selectedIndex.value = clamped;
    nextTick(() => {
      const el = itemRefs.get(clamped);
      if (el) {
        el.focus();
        // Keep the focused item visible without jumping the page
        try {
          el.scrollIntoView({ block: "nearest", inline: "nearest" });
        } catch {
          // no-op
        }
      }
    });
  }

  function focusFirst() {
    if (items.value.length > 0) {
      focusItemAt(0);
    } else {
      pendingFocusAfterLoad.value = true;
    }
  }

  function getColumnCount(): number {
    if (itemRefs.size === 0) return 1;
    const elements = Array.from(itemRefs.values());
    let minTop = Number.POSITIVE_INFINITY;
    for (const el of elements) {
      if (!el) continue;
      const top = el.offsetTop;
      if (top < minTop) minTop = top;
    }
    if (!isFinite(minTop)) return 1;
    let countInFirstRow = 0;
    for (const el of elements) {
      if (!el) continue;
      if (Math.abs(el.offsetTop - minTop) < 2) countInFirstRow += 1;
    }
    return countInFirstRow > 0 ? countInFirstRow : 1;
  }

  function onInputKeydown(e: KeyboardEvent) {
    if (e.key === "Enter") {
      e.preventDefault();
      focusFirst();
    }
  }

  function onListKeydown(e: KeyboardEvent) {
    const length = items.value.length;
    if (length === 0) return;

    if (e.key === "Escape") {
      e.preventDefault();
      e.stopPropagation();
      selectedIndex.value = null;
      if (onEscapeFocus) onEscapeFocus();
      return;
    }

    if (e.key === "ArrowRight") {
      e.preventDefault();
      const next = selectedIndex.value == null ? 0 : selectedIndex.value + 1;
      focusItemAt(next >= length ? length - 1 : next);
      return;
    }

    if (e.key === "ArrowLeft") {
      e.preventDefault();
      const prev = selectedIndex.value == null ? 0 : selectedIndex.value - 1;
      focusItemAt(prev < 0 ? 0 : prev);
      return;
    }

    if (e.key === "ArrowDown") {
      e.preventDefault();
      const cols = getColumnCount();
      const step = Math.max(cols, 1);
      const next = selectedIndex.value == null ? 0 : selectedIndex.value + step;
      focusItemAt(next >= length ? length - 1 : next);
      return;
    }

    if (e.key === "ArrowUp") {
      e.preventDefault();
      const cols = getColumnCount();
      const step = Math.max(cols, 1);
      const prev = selectedIndex.value == null ? 0 : selectedIndex.value - step;
      focusItemAt(prev < 0 ? 0 : prev);
      return;
    }

    if (e.key === "Enter") {
      if (selectedIndex.value != null) {
        e.preventDefault();
        const item = items.value[selectedIndex.value];
        if (item) onSelect(item);
      }
    }
  }

  function setItemRef(el: HTMLElement | null, index: number) {
    if (el) {
      itemRefs.set(index, el);
    } else {
      itemRefs.delete(index);
    }
  }

  function setContainerRef(el: HTMLElement | null) {
    containerRef.value = el;
  }

  watch(
    () => items.value.length,
    () => {
      // Clamp selection within bounds when list changes
      if (selectedIndex.value != null) {
        const clamped = clampIndex(selectedIndex.value);
        if (clamped !== selectedIndex.value) {
          selectedIndex.value = clamped >= 0 ? clamped : null;
        }
      }

      if (pendingFocusAfterLoad.value && items.value.length > 0) {
        pendingFocusAfterLoad.value = false;
        focusItemAt(0);
      }
    }
  );

  onBeforeUnmount(() => {
    itemRefs.clear();
  });

  return {
    selectedIndex,
    setItemRef,
    setContainerRef,
    onInputKeydown,
    onListKeydown,
    focusFirst
  };
}
